// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: xguo

#include "coin2/exchange/deribit_options/order_v2/parser.h"

#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/deribit_base/api_order/fix/SecurityList.h"
#include "coin2/exchange/deribit_base/order_v2/parser.h"
#include "coin2/exchange/deribit_options/symbology/product_info.h"
#include "coin2/exchange/util/json_util.h"
#include "quickfix/Message.h"
#include "quickfix/fix44/SecurityList.h"

namespace coin2::exchange::deribit_futures::order_v2 {

namespace {

using namespace coin2::exchange::deribit::order_v2;

}  // namespace

DeribitOptionsParser::DeribitOptionsParser() {}

void DeribitOptionsParser::ParseRestAuthToken() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAuthToken] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  auto token = ParseAuthToken(document);
  LOG(INFO) << "token string: " << token;
  if (!token.empty()) {
    mb_->PublishEvent("deribit/token", &token);
    result_.set_type(MsgType::IGNORABLE);
  } else {
    LOG(ERROR) << "Invalid token response: " << JsonToString(document);
  }
}

std::string DeribitOptionsParser::ParseAuthToken(const JsonValue& doc) {
  /*
  {
    "jsonrpc": "2.0",
    "result": {
      "token_type": "bearer",
      "scope": "account:read_write block_trade:read_write custody:read_write
       mainaccount session:rest-8+pk4aGdvRQ= trade:read_write wallet:read_write",
      "refresh_token": "1629856775341.1LjL3GR_.WttGl9aWTLzox5UXNUgdM5t8L66YmCPU_
      KNjvYDiow4g6bRTQAlFmClhw3QKd_NjIv4EjxtozT4VG3M2bGBuVSufkKgSwQhB4iT3L_xDUwB
      bADC7BdMLNjW70snqm3Vlgl7SYg-Gmhj1bp1ZsmZ58a4R9UxGwXn1zboMJrTOspqRXk0VMkZME
      o-8WC6hIlZCU1PvtiYTh0uEdng8omPAysg5LuTs3aJY9KERHRL4u7j1kJ9Mm6dUShR0M5AgUcg
      EQvu5iPklCCsYVJmedTtPkuQUaqq-pxpX0LfUEZHVVlE6q1vMZpVCGbQ24PP_6DwN2jBeYJfo4
      GtawUU",
      "expires_in": 900,
      "access_token": "1629252875341.1JqDZQqp.cNRJ54xi23jdTNH8AM6Zm4flAsQ0mo8rgK
      Vykjx-mm6ox9PKsqu4VsWKCMIqV0LtC0fXbBOuO7Rh7nuKlVtqJXOaRBbUmakffMWFhlBJe92C
      MajJfHpU7xigrrNRvpTeSl5mB_2EiBS_fxquPiRywIZAF6l2z_kUFGmA_xxK0T5hvobhlHXvMm
      iqNZaGSNo1bYfCO3_lnjElpBx2WRRV1dWHv-2d_GAfM2b0pHFgWzvZQrCqilqesWoAVJ890bv8
      ZlE1tA2fY3KGzellxkTI0F8pMdVzgQbo8WaaLAEObsPErPSeoXNVD0oQX5bxiqabtjUqDCEwIf
      kk6fsD"
    },
    "usIn": 1629251975341281,
    "usOut": 1629251975341736,
    "usDiff": 455,
    "testnet": true
  }
  */
  if (!doc.IsObject()) {
    LOG(ERROR) << "[ParseAuthToken] "
               << "Invalid json object";
    return "";
  }

  if (IsDeribitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseAuthToken] " << JsonToString(doc);
    return "";
  }

  if (!doc.HasMember("result") || !doc["result"].HasMember("access_token") ||
      !doc["result"].HasMember("refresh_token")) {
    LOG(ERROR) << "[ParseAuthToken] Unexpected auth format!" << JsonToString(doc);
    return "";
  }

  std::string token_str = std::string(doc["result"]["access_token"].GetString()) + "\n" +
                          std::string(doc["result"]["refresh_token"].GetString());  // + "\n" +
  // std::string(doc["result"]["expires_in"].GetString()) + "\n" +
  // std::string(doc["result"]["refresh_token"].GetString());*/

  return token_str;
}

void DeribitOptionsParser::ParseRestAccountBalance() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (is_balance_initialized_) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
    *result_.mutable_account_balance() = std::move(account_balance);
  } else {
    LOG(INFO) << "account balance not all received yet!";
    result_.set_type(MsgType::IGNORABLE);
  }
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance DeribitOptionsParser::ParseBalance(const JsonValue& doc) {
  /*
  {
    "jsonrpc": "2.0",
    "result": [
      {
        "username": "plhytest",
        "type": "main",
        "system_name": "plhytest",
        "security_keys_enabled": false,
        "security_keys_assignments": [],
        "referrals_count": 0,
        "receive_notifications": false,
        "portfolio": {
          "usdt": {
            "margin_balance": 0,
            "maintenance_margin": 0,
            "initial_margin": 0,
            "equity": 0,
            "currency": "usdt",
            "balance": 0,
            "available_withdrawal_funds": 0,
            "available_funds": 0
          },
          "eth": {
            "margin_balance": 0,
            "maintenance_margin": 0,
            "initial_margin": 0,
            "equity": 0,
            "currency": "eth",
            "balance": 0,
            "available_withdrawal_funds": 0,
            "available_funds": 0
          },
          "btc": {
            "margin_balance": 0,
            "maintenance_margin": 0,
            "initial_margin": 0,
            "equity": 0,
            "currency": "btc",
            "balance": 0,
            "available_withdrawal_funds": 0,
            "available_funds": 0
          }
        },
        "login_enabled": true,
        "is_password": true,
        "id": 32726,
        "email": "huayuan@prestolabs.io"
      }
    ],
    "usIn": 1629253073734506,
    "usOut": 1629253073734766,
    "usDiff": 260,
    "testnet": true
  }
  */
  AccountBalance acc_balance;

  if (!doc.IsObject()) {
    LOG(ERROR) << "[ParseBalance] "
               << "Invalid json object";
    return acc_balance;
  }

  if (IsDeribitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] " << JsonToString(doc);
    return acc_balance;
  }

  const JsonValue* pinfo;
  bool is_rest_portfolio = false;
  if (doc.HasMember("params") && doc["params"].HasMember("channel") &&
      doc["params"].HasMember("data")) {
    pinfo = &doc["params"]["data"];
  } else if (doc.HasMember("result") && doc["result"].IsObject()) {
    pinfo = &doc["result"];
  } else if (doc.HasMember("result") && doc["result"].IsArray()) {
    if ((doc["result"].Size() <= 0) || !doc["result"][0].HasMember("portfolio")) {
      LOG(ERROR) << "[ParseBalance] Unexpected balance format!" << JsonToString(doc);
      return acc_balance;
    }
    pinfo = &doc["result"];
    is_rest_portfolio = true;
  } else {
    LOG(INFO) << "Not account subaccounts: " << JsonToString(doc);
    return acc_balance;
  }

  acc_balance.set_exchange("Deribit");
  acc_balance.set_market_type("Options");
  const JsonValue& info = *pinfo;
  // account summary && user.portfolio.COIN
  if (!is_rest_portfolio) {
    const char* native_currency = info["currency"].GetString();
    if (!symcache_->has_currency_native(native_currency)) {
      DLOG(WARNING) << "Unknown currency: " << native_currency;
      return acc_balance;
    }

    auto* currency = symcache_->GetCurrencyByNative(native_currency);
    double total = stod(info["margin_balance"].GetString());
    double avail = stod(info["available_funds"].GetString());
    double hold = total - avail;
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    currency_balance->set_available(avail);
    currency_balance->set_hold(hold);
    currency_balance->set_total(total);
    if (!is_balance_initialized_) {
      balance_map_.emplace(native_currency, *currency_balance);
      std::set<std::string> currencies;
      for (const auto* each : symcache_->GetCurrencies()) {
        currencies.emplace(each->symbol());
      }
      // USD not take into account.
      if (balance_map_.size() == currencies.size() - 1) {
        acc_balance.mutable_each_balance()->Clear();
        for (const auto& each : balance_map_) {
          auto* bal = acc_balance.add_each_balance();
          bal->MergeFrom(each.second);
        }
        is_balance_initialized_ = true;
        LOG(INFO) << "deribit balance is ready!" << acc_balance.DebugString();
      } else {
        for (const auto& each : balance_map_) {
          LOG(INFO) << "native_currency: " << each.first;
        }
        for (const auto& each : currencies) {
          LOG(INFO) << "symcache native_currency: " << each << "##";
        }
      }
    }
    return acc_balance;
  }

  // get subaccounts
  for (auto& data : doc["result"][0]["portfolio"].GetObject()) {
    if (!data.value.HasMember("currency") || !data.value.HasMember("balance")) {
      continue;
    }
    if (!symcache_->has_currency_native(data.value["currency"].GetString())) {
      continue;
    }
    if (!data.value.HasMember("initial_margin") || !data.value.HasMember("margin_balance")) {
      continue;
    }
    double hold = stod(data.value["initial_margin"].GetString());
    double total = stod(data.value["margin_balance"].GetString());
    double avail = total - hold;
    auto* currency = symcache_->GetCurrencyByNative(data.value["currency"].GetString());
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    currency_balance->set_available(avail);
    currency_balance->set_hold(hold);
    currency_balance->set_total(total);
  }
  return acc_balance;
}

void DeribitOptionsParser::ParseRestAccountPosition() {
  JsonDocument doc;
  if (!PrepareJsonDocument(&doc)) {
    LOG(ERROR) << "[ParseRestAccountPosition] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_position = ParsePosition(doc);
  if (!account_position.has_exchange() || !account_position.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (is_position_initialized_) {
    result_.set_type(MsgType::ACCOUNT_POSITION);
    *result_.mutable_account_position() = std::move(account_position);
  } else {
    LOG(INFO) << "account position not all received yet!";
    result_.set_type(MsgType::IGNORABLE);
  }
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountPosition DeribitOptionsParser::ParsePosition(const JsonValue& doc) {
  /*
  {
    "jsonrpc": "2.0",
    "id": 2236,
    "result": [
      {
          "average_price": 7440.18,
          "delta": 0.006687487,
          "direction": "buy",
          "estimated_liquidation_price": 1.74,
          "floating_profit_loss": 0,
          "index_price": 7466.79,
          "initial_margin": 0.000197283,
          "instrument_name": "BTC-PERPETUAL",
          "kind": "future",
          "leverage": 34,
          "maintenance_margin": 0.000143783,
          "mark_price": 7476.65,
          "open_orders_margin": 0.000197288,
          "realized_funding": -1e-8,
          "realized_profit_loss": -9e-9,
          "settlement_price": 7476.65,
          "size": 50,
          "size_currency": 0.006687487,
          "total_profit_loss": 0.000032781
      }
    ]
  }
  */
  AccountPosition acc_position;

  if (!doc.IsObject()) {
    LOG(ERROR) << "[ParsePosition] "
               << "Invalid json object";
    return acc_position;
  }

  if (IsDeribitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return acc_position;
  }

  if (!doc.HasMember("result") || !doc["result"].IsArray()) {
    LOG(ERROR) << "[ParsePosition] " << JsonToString(doc);
    return acc_position;
  }

  acc_position.set_exchange("Deribit");
  acc_position.set_market_type("Options");
  std::set<std::string> symbols_in_msg;
  std::string base = currency_base_;
  for (const auto& data : doc["result"].GetArray()) {
    if (!data.HasMember("instrument_name") || !data.HasMember("size")) {
      LOG(ERROR) << "Unexpected position data! " << JsonToString(data);
      continue;
    }
    const char* native_symbol = data["instrument_name"].GetString();
    // if (base.empty()) {
    //   base = GetBase(native_symbol);
    // } else {
    //   CHECK_EQ(base, GetBase(native_symbol));
    // }
    if (!symcache_->has_product_native(native_symbol)) {
      auto* position = acc_position.add_each_position();
      position->set_native_symbol(native_symbol);
      double pos_amt = stod(data["size"].GetString());
      if (pos_amt >= 0.) {
        position->set_long_position(pos_amt);
      } else {
        position->set_short_position(-pos_amt);
      }
      position->set_net_position(pos_amt);
      if (!is_position_initialized_) {
        undetected_position_map_.emplace(native_symbol, *position);
        LOG(INFO) << "position: " << position->DebugString();
      }
      continue;
    }
    auto* product = symcache_->GetProductByNative(native_symbol);
    auto* position = acc_position.add_each_position();
    position->set_symbol(product->symbol());
    position->set_native_symbol(product->order_native_symbol());
    symbols_in_msg.insert(position->native_symbol());
    double pos_amt = stod(data["size"].GetString());
    if (pos_amt >= 0.) {
      position->set_long_position(pos_amt);
    } else {
      position->set_short_position(-pos_amt);
    }
    position->set_net_position(pos_amt);

    if (!is_position_initialized_) {
      position_map_.emplace(product->symbol(), *position);
      LOG(INFO) << "position: " << position->DebugString();
    }
  }
  for (const auto* prod : symcache_->GetProducts()) {
    std::string base_currency = prod->base().native_currency();
    if (base_currency == base && symbols_in_msg.count(prod->native_symbol()) == 0) {
      auto* position = acc_position.add_each_position();
      position->set_symbol(prod->symbol());
      position->set_native_symbol(prod->order_native_symbol());
      position->set_net_position(0);
      if (!is_position_initialized_) {
        position_map_.emplace(prod->symbol(), *position);
        LOG(INFO) << "position: " << position->DebugString();
      }
    }
  }

  if (!is_position_initialized_) {
    if (position_map_.size() == symcache_->GetProducts().size()) {
      acc_position.mutable_each_position()->Clear();
      for (const auto& each : position_map_) {
        auto* pos = acc_position.add_each_position();
        pos->MergeFrom(each.second);
      }
      for (const auto& each : undetected_position_map_) {
        auto* pos = acc_position.add_each_position();
        pos->MergeFrom(each.second);
      }
      is_position_initialized_ = true;
      LOG(INFO) << "deribit position is ready!" << acc_position.DebugString();
    } else {
      for (const auto& each : position_map_) {
        LOG(INFO) << "product: " << each.first;
      }
      for (const auto& each : undetected_position_map_) {
        LOG(INFO) << "undetected product native symbol: " << each.first;
      }
      for (const auto& each : symcache_->GetProducts()) {
        LOG(INFO) << "symcache product: " << each->symbol();
      }
    }
  }
  return acc_position;
}

bool DeribitOptionsParser::ParseWsPositionFromChanges(
    const JsonValue& doc,
    AccountPosition* acc_position) {
  acc_position->Clear();

  if (!doc.IsObject()) {
    LOG(ERROR) << "[ParseWsPositionFromChanges] Invalid json object";
    return false;
  }

  if (IsDeribitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseWsPositionFromChanges] Error message" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("params") || !doc["params"].HasMember("channel") ||
      !doc["params"].HasMember("data") || !doc["params"]["data"].HasMember("positions") ||
      !doc["params"]["data"]["positions"].IsArray()) {
    LOG(ERROR) << "[ParseWsPositionFromChanges] Unexpected positions format" << JsonToString(doc);
    return false;
  }

  acc_position->set_exchange("Deribit");
  acc_position->set_market_type("Options");
  const auto& position_list = doc["params"]["data"]["positions"];
  if (position_list.GetArray().Size() == 0) {
    return false;
  }

  for (const auto& data : position_list.GetArray()) {
    if (!data.HasMember("instrument_name") || !data.HasMember("size")) {
      LOG(ERROR) << "[ParseWsPositionFromChanges] Unexpected position format" << JsonToString(data);
      continue;
    }

    auto* position = acc_position->add_each_position();
    position->set_native_symbol(data["instrument_name"].GetString());
    double pos_amt = stod(data["size"].GetString());
    if (pos_amt >= 0.) {
      position->set_long_position(pos_amt);
    } else {
      position->set_short_position(-pos_amt);
    }
    position->set_net_position(pos_amt);
  }
  return true;
}

bool DeribitOptionsParser::ParseWsOrderFromChanges(
    const JsonValue& doc,
    ProductOrderBundle* product_order_bundle) {
  product_order_bundle->Clear();

  if (!doc.IsObject()) {
    LOG(ERROR) << "[ParseWsOrderFromChanges] Invalid json object";
    return false;
  }

  if (IsDeribitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseWsOrderFromChanges] Error message" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("params") || !doc["params"].HasMember("channel") ||
      !doc["params"].HasMember("data") || !doc["params"]["data"].HasMember("orders")) {
    LOG(ERROR) << "[ParseWsOrderFromChanges] Unexpected orders format" << JsonToString(doc);
    return false;
  }

  const auto& order_list = doc["params"]["data"]["orders"];
  if (order_list.GetArray().Size() == 0) {
    return false;
  }

  for (const auto& info : order_list.GetArray()) {
    auto* order = product_order_bundle->add_each_order();
    order->MergeFrom(ParseRestOrderInfo(info));
  }
  return true;
}

bool DeribitOptionsParser::ParseWsTradeFromChanges(
    const JsonValue& doc,
    ProductFillBundle* product_fill_bundle) {
  product_fill_bundle->Clear();

  if (!doc.IsObject()) {
    LOG(ERROR) << "[ParseWsTradeFromChanges] Invalid json object";
    return false;
  }

  if (IsDeribitFuturesErrMessage(doc)) {
    LOG(ERROR) << "[ParseWsTradeFromChanges] Error message" << JsonToString(doc);
    return false;
  }

  if (!doc.HasMember("params") || !doc["params"].HasMember("channel") ||
      !doc["params"].HasMember("data") || !doc["params"]["data"].HasMember("trades")) {
    LOG(ERROR) << "[ParseWsTradeFromChanges] Unexpected trades format" << JsonToString(doc);
    return false;
  }

  const auto& trade_list = doc["params"]["data"]["trades"];

  if (trade_list.GetArray().Size() == 0) {
    return false;
  }

  for (const auto& info : trade_list.GetArray()) {
    if (!info.HasMember("instrument_name") || !info.HasMember("order_id") ||
        !info.HasMember("price") || !info.HasMember("amount") || !info.HasMember("trade_id")) {
      LOG(ERROR) << "[ParseWsTradeFromChanges] Unexpected trade format" << JsonToString(doc);
      continue;
    }
    auto* fill = product_fill_bundle->add_each_fill();
    const auto& symbol = std::string(info["instrument_name"].GetString());
    fill->set_symbol(symbol);
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(symbol + std::string(info["order_id"].GetString()));
    fill->set_price(stod(info["price"].GetString()));
    fill->set_qty(stod(info["amount"].GetString()));
    fill->set_fill_id(symbol + "#" + info["trade_seq"].GetString());
    if (info["liquidity"].GetString()[0] == 'M') {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (info["liquidity"].GetString()[0] == 'T') {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      LOG(WARNING) << "taker/maker info not available!";
    }
    fill->set_fee(stod(info["fee"].GetString()));
    fill->set_fee_currency(info["fee_currency"].GetString());
  }
  return true;
}

ProductOrderElement DeribitOptionsParser::ParseRestOrderInfo(const JsonValue& data) {
  ProductOrderElement order;
  std::string exchange_id =
      std::string(data["instrument_name"].GetString()) + std::string(data["order_id"].GetString());
  order.set_market_type(MarketType::Options);
  order.set_exchange_type(ExchangeType::Deribit);
  order.set_api_version("v2");
  order.set_exchange_order_id(exchange_id);
  order.set_symbol(data["instrument_name"].GetString());
  order.set_price(stod(data["price"].GetString()));
  order.set_qty(stod(data["amount"].GetString()));
  order.set_proc_order_id(GetProcOrderId(data));
  order.set_side(GetOrderSide(data["direction"].GetString()));
  order.set_order_type(GetOrderType(data["order_type"].GetString()));
  order.set_timestamp(msg_->timestamp);
  order.set_created_timestamp(stoll(data["creation_timestamp"].GetString()) * 1000000);
  SetOrderStatus(data["order_state"].GetString(), &order);
  return order;
}

void DeribitOptionsParser::ParseRestOrderInfoList() {
  /*
  {
    "jsonrpc": "2.0",
    "id": 1953,
    "result": [
      {
        "time_in_force": "good_til_cancelled",
        "reduce_only": false,
        "profit_loss": 0,
        "price": 0.0028,
        "post_only": false,
        "order_type": "limit",
        "order_state": "open",
        "order_id": "146062",
        "max_show": 10,
        "last_update_timestamp": 1550050597036,
        "label": "",
        "is_liquidation": false,
        "instrument_name": "BTC-15FEB19-3250-P",
        "filled_amount": 0,
        "direction": "buy",
        "creation_timestamp": 1550050597036,
        "commission": 0,
        "average_price": 0,
        "api": true,
        "amount": 10
      }
    ]
  }
  */
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestOrderInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!data.IsObject()) {
    LOG(ERROR) << "[ParseRestOrderInfoList] "
               << "Invalid json object";
    return;
  }

  if (IsDeribitFuturesErrMessage(data)) {
    LOG(ERROR) << "[ParseRestOrderInfoList] " << JsonToString(data);
    return;
  }

  JsonValue order_list;
  if (data.HasMember("params") && data["params"].HasMember("channel") &&
      data["params"].HasMember("data")) {
    // ws
    order_list = data["params"]["data"];
  } else {
    // rest
    if (!data.HasMember("result") || !data["result"].IsArray()) {
      LOG(ERROR) << "[ParseRestOrderInfoList] Unexpected order list format!" << JsonToString(data);
      return;
    }
    order_list = data["result"];
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  if (!order_list.IsArray()) {
    // ws
    auto* order = product_order_bundle->add_each_order();
    order->MergeFrom(ParseRestOrderInfo(order_list));
    product_order_bundle_found = true;
  } else {
    // rest
    for (const auto& info : order_list.GetArray()) {
      auto* order = product_order_bundle->add_each_order();
      order->MergeFrom(ParseRestOrderInfo(info));
      product_order_bundle_found = true;
    }
  }
  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void DeribitOptionsParser::ParseRestFillInfoList() {
  /*
  {
    "jsonrpc": "2.0",
    "id": 9367,
    "result": {
      "trades": [
        {
          "underlying_price": 204.5,
          "trade_seq": 3,
          "trade_id": "ETH-2696060",
          "timestamp": 1590480363130,
          "tick_direction": 2,
          "state": "filled",
          "self_trade": false,
          "reduce_only": false,
          "price": 0.361,
          "post_only": false,
          "order_type": "limit",
          "order_id": "ETH-584827850",
          "matching_id": null,
          "mark_price": 0.364585,
          "liquidity": "T",
          "iv": 0,
          "instrument_name": "ETH-29MAY20-130-C",
          "index_price": 203.72,
          "fee_currency": "ETH",
          "fee": 0.002,
          "direction": "sell",
          "amount": 5
        },
        {
          "underlying_price": 204.82,
          "trade_seq": 3,
          "trade_id": "ETH-2696062",
          "timestamp": 1590480416119,
          "tick_direction": 0,
          "state": "filled",
          "self_trade": false,
          "reduce_only": false,
          "price": 0.015,
          "post_only": false,
          "order_type": "limit",
          "order_id": "ETH-584828229",
          "matching_id": null,
          "mark_price": 0.000596,
          "liquidity": "T",
          "iv": 352.91,
          "instrument_name": "ETH-29MAY20-140-P",
          "index_price": 204.06,
          "fee_currency": "ETH",
          "fee": 0.002,
          "direction": "buy",
          "amount": 5
        }
      ],
      "has_more": true
    }
  }

  {
    "jsonrpc": "2.0",
    "method": "subscription",
    "params": {
      "channel": "user.trades.future.ETH.raw",
      "data": [
        {
          "trade_seq": "67206925",
          "trade_id": "ETH-88693837",
          "timestamp": "1629870170463",
          "tick_direction": "2",
          "state": "filled",
          "self_trade": false,
          "reduce_only": false,
          "profit_loss": "0.0",
          "price": "3204.95",
          "post_only": true,
          "order_type": "limit",
          "order_id": "ETH-14245097606",
          "matching_id": null,
          "mark_price": "3205.47",
          "liquidity": "M",
          "label": "1629870148793231212",
          "instrument_name": "ETH-PERPETUAL",
          "index_price": "3204.39",
          "fee_currency": "ETH",
          "fee": "-3.1e-7",
          "direction": "buy",
          "amount": "10.0"
        }
      ]
    }
  }
  */
  JsonDocument data;
  if (!PrepareJsonDocument(&data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (!data.IsObject()) {
    LOG(ERROR) << "[ParseRestFillInfoList] "
               << "Invalid json object";
    return;
  }

  if (IsDeribitFuturesErrMessage(data)) {
    LOG(ERROR) << "[ParseRestFillInfoList] " << JsonToString(data);
    return;
  }

  JsonValue fill_list;
  if (data.HasMember("params") && data["params"].HasMember("channel") &&
      data["params"].HasMember("data")) {
    // ws
    fill_list = data["params"]["data"];
  } else {
    if (!data.HasMember("result") || !data["result"].HasMember("trades") ||
        !data["result"]["trades"].IsArray()) {
      LOG(ERROR) << "[ParseRestFillInfoList] Unexpected fill list format!" << JsonToString(data);
      return;
    }
    fill_list = data["result"]["trades"];
  }
  CHECK(fill_list.IsArray()) << JsonToString(data);

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& info : fill_list.GetArray()) {
    const auto& symbol = std::string(info["instrument_name"].GetString());
    auto* fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(symbol);
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(symbol + std::string(info["order_id"].GetString()));
    fill->set_price(stod(info["price"].GetString()));
    fill->set_qty(stod(info["amount"].GetString()));
    fill->set_fill_id(symbol + "#" + info["trade_seq"].GetString());
    if (info["liquidity"].GetString()[0] == 'M') {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (info["liquidity"].GetString()[0] == 'T') {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      LOG(WARNING) << "taker/maker info not available!";
    }
    fill->set_fee(stod(info["fee"].GetString()));
    fill->set_fee_currency(info["fee_currency"].GetString());
    product_fill_bundle_found = true;
  }
  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void DeribitOptionsParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[ParseSubmitResponse] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsDeribitFuturesErrMessage(json)) {
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    resp->set_success(false);
    int error_code = std::stoi(resp->exchange_error_code());
    if (error_code == 11054) {
      // if post only reject
      resp->set_error_code(coin::proto::OrderErrorCode::POST_ONLY_REJECT);
    } else {
      LOG(ERROR) << "[ParseSubmitResponse] Error Response" << http_context->res;
    }
    return;
  }

  if (!json.HasMember("result") || !json["result"].HasMember("order") ||
      !json["result"]["order"].HasMember("instrument_name") ||
      !json["result"]["order"].HasMember("order_id")) {
    resp->set_error_code(coin::proto::OrderErrorCode::INVALID_JSON);
    resp->set_success(false);
    return;
  }

  // Order state: "open", "filled", "rejected", "cancelled", "untriggered"
  const std::string_view order_state(json["result"]["order"]["order_state"].GetString());
  if (order_state == "rejected") {
    resp->set_error_code(coin::proto::OrderErrorCode::POST_ONLY_REJECT);
    resp->set_success(false);
    return;
  }

  if (order_state == "cancelled") {
    resp->set_error_code(coin::proto::OrderErrorCode::POST_ONLY_REJECT);
    resp->set_success(false);
    return;
  }

  resp->set_success(true);
  resp->set_exchange_order_id(
      json["result"]["order"]["instrument_name"].GetString() +
      std::string(json["result"]["order"]["order_id"].GetString()));
}

void DeribitOptionsParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.base().result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus Error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  if (http_context->res.result_int() == 429) {
    LOG(ERROR) << "[ParseCancelResponse] API_LIMIT_ERROR";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parse Error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (IsDeribitFuturesErrMessage(json)) {
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    resp->set_success(false);
    int error_code = std::stoi(resp->exchange_error_code());
    if (error_code == 11044) {
      // if order already dead
      resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NOT_FOUND);
    } else {
      LOG(ERROR) << "[ParseCancelResponse] Error Response" << http_context->res;
    }
    return;
  }

  if (!json.HasMember("result") || !json["result"].HasMember("instrument_name") ||
      !json["result"].HasMember("order_id")) {
    resp->set_error_code(coin::proto::OrderErrorCode::INVALID_JSON);
    resp->set_success(false);
    return;
  }

  const std::string_view order_state(json["result"]["order_state"].GetString());
  bool cancel_confirmed = false;
  if (order_state == "cancelled") {
    cancel_confirmed = true;
  }

  resp->set_success(true);
  resp->set_exchange_order_id(
      json["result"]["instrument_name"].GetString() +
      std::string(json["result"]["order_id"].GetString()));
  resp->set_cancel_confirmed(cancel_confirmed);
}

void DeribitOptionsParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  if (IsAccountUpdate(document)) {
    ParseRestAccountBalance();
  } else if (IsOrderUpdate(document)) {
    ParseRestOrderInfoList();
    result_.set_type(MsgType::ORDER_STATUS);
  } else if (IsFillUpdate(document)) {
    ParseRestFillInfoList();
    result_.set_type(MsgType::ORDER_FILL);
  } else if (IsChangeUpdate(document)) {
    // order
    if (ParseWsOrderFromChanges(document, result_.mutable_order_list())) {
      result_.set_type(MsgType::ORDER_STATUS);
    }
    // trade
    if (ParseWsTradeFromChanges(document, result_.mutable_fill_list())) {
      result_.set_type(MsgType::ORDER_FILL);
    } else {
      return;
    }
    // position
    if (ParseWsPositionFromChanges(document, result_.mutable_account_position())) {
      result_.set_type(MsgType::ACCOUNT_POSITION);
    }
  }
}

void DeribitOptionsParser::ParseFixMessage() {
  using namespace coin2::exchange::deribit::order_v2;
  if (!msg_ || msg_->fix_message == nullptr) {
    return;
  }
  const FIX::Message& message = *(msg_->fix_message);
  FIX::MsgType msg_type;
  if (!message.getHeader().getFieldIfSet(msg_type)) {
    return;
  }
  if (MSG_TYPE_SESSION_REJECT == msg_type) {
    LOG(ERROR) << "msg type session reject received!";
  } else if (MSG_TYPE_EXECUTION_REPORT == msg_type) {
    ParseFixSubmitResponse(message);
    // ParseFixFillInfo(message);
    ParseFixOrderInfo(message);
  } else if (MSG_TYPE_ORDER_CANCEL_REJECT == msg_type) {
    ParseFixCancelResponse(message);
  } else if (MSG_TYPE_SECURITY_LIST == msg_type) {
    ParseFixSecurityList(message);
  }
}

void DeribitOptionsParser::ParseFixSecurityList(const FIX::Message& message) {
  result_.set_type(MsgType::IGNORABLE);
  using coin2::exchange::deribit::api_order::fix::SecurityList;
  SecurityList security_list(message);
  FIX::SecurityRequestResult security_request_result;
  if (!security_list.getFieldIfSet(security_request_result)) {
    LOG(INFO) << "SecurityRequestResult not found!";
    return;
  }
  if (0 != security_request_result) {
    return;
  }
  FIX::NoRelatedSym no_related_sym_cnt;
  if (!security_list.getFieldIfSet(no_related_sym_cnt)) {
    return;
  }
  if (0 >= no_related_sym_cnt) {
    return;
  }

  for (int i = 1; i <= no_related_sym_cnt; ++i) {
    FIX44::SecurityList::NoRelatedSym no_related_sym;
    security_list.getGroup(i, no_related_sym);
    FIX::Symbol symbol;
    if (!no_related_sym.getFieldIfSet(symbol)) {
      continue;
    }
    FIX::ContractMultiplier contract_multiplier;
    if (!no_related_sym.getFieldIfSet(contract_multiplier)) {
      continue;
    }
    SetContractValue(symbol, contract_multiplier);
  }
}

void DeribitOptionsParser::ParseFixSubmitResponse(const FIX::Message& message) {
  using namespace coin2::exchange::deribit::order_v2;
  FIX::OrdStatus order_state;
  if (!message.getFieldIfSet(order_state)) {
    LOG(INFO) << "exec type not found!";
    return;
  }
  if (ORDER_STATE_REJECTED != order_state) {
    return;
  }

  int64_t proc_order_id{0};
  FIX::OrigClOrdID orig_order_id;
  FIX::Symbol symbol;

  if (!message.getFieldIfSet(symbol)) {
    LOG(ERROR) << "symbol not found. " << message;
    return;
  }
  const std::string native_symbol = symbol.getValue();
  if (!symcache_->has_product_native(native_symbol)) {
    LOG(INFO) << "[DeribitOptionsParser::ParseFixSubmitResponse] ignore unknown product: "
              << native_symbol;
    return;
  }
  if (!message.getFieldIfSet(orig_order_id)) {
    LOG(ERROR) << "proc_order_id not found. " << message;
    return;
  }

  proc_order_id = ToInt64(orig_order_id.getValue());
  SubmitOrderResponse& resp = *result_.mutable_submit_response();
  resp.Clear();
  resp.set_symbol(native_symbol);
  resp.set_proc_order_id(proc_order_id);
  resp.set_timestamp(msg_->timestamp);
  resp.set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);

  /*
   * FIX::OrdRejReason error_code;
   * if (message.getFieldIfSet(error_code)) {
   * }
   * Deribit msg has OrdRejReason, the but meaning of code is
   * not documented.
   */

  FIX::Text error_reason;
  if (message.getFieldIfSet(error_reason)) {
    resp.set_exchange_error_msg(error_reason.getValue());
    if (error_reason == "rejected: post_only") {
      resp.set_error_code(coin::proto::OrderErrorCode::POST_ONLY_REJECT);
    }
  }
  resp.set_success(false);
  result_.set_type(MsgType::SUBMIT_RESPONSE);
}

void DeribitOptionsParser::ParseFixCancelResponse(const FIX::Message& message) {
  using namespace coin2::exchange::deribit::order_v2;
  FIX::MsgType msg_type;
  if (!message.getHeader().getFieldIfSet(msg_type)) {
    return;
  }
  if (MSG_TYPE_ORDER_CANCEL_REJECT != msg_type) {
    return;
  }

  FIX::ClOrdID order_id;
  FIX::OrigClOrdID client_id;
  if (!message.getFieldIfSet(client_id)) {
    LOG(ERROR) << "proc_order_id not found. " << message;
    return;
  }
  if (!message.getFieldIfSet(order_id)) {
    LOG(ERROR) << "order_id not found. " << message;
    return;
  }

  int64_t proc_order_id{0};
  proc_order_id = ToInt64(client_id.getValue());
  CancelOrderResponse& resp = *result_.mutable_cancel_response();
  resp.Clear();
  resp.set_proc_order_id(proc_order_id);
  resp.set_exchange_order_id(order_id.getValue());
  resp.set_timestamp(msg_->timestamp);

  // fix this part
  resp.set_error_code(coin::proto::OrderErrorCode::ORDER_UNKNOWN_ERROR);
  FIX::OrdStatus order_state;
  if (message.getFieldIfSet(order_state)) {
    if ('2' == order_state.getValue()) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_FILLED_ALREADY);
    } else if ('4' == order_state.getValue()) {
      resp.set_error_code(coin::proto::OrderErrorCode::ORDER_NOT_FOUND);
    } else {
      LOG(WARNING) << "Undocumented error code found: " << order_state.getValue()
                   << ", fix_message: " << message.toString();
    }
  }

  FIX::Text error_reason;
  if (message.getFieldIfSet(error_reason)) {
    resp.set_exchange_error_msg(error_reason.getValue());
  }
  resp.set_success(false);
  result_.set_type(MsgType::CANCEL_RESPONSE);
}

void DeribitOptionsParser::ParseFixFillInfo(const FIX::Message& message) {
  using namespace coin2::exchange::deribit::order_v2;
  FIX::OrdStatus order_state;
  if (!message.getFieldIfSet(order_state)) {
    return;
  }
  if (ORDER_STATE_FILL != order_state && ORDER_STATE_PART_FILL != order_state) {
    return;
  }
  FIX::ClOrdID order_id;
  FIX::OrigClOrdID client_id;
  FIX::Symbol symbol;

  FIX::NoFills num_fills;
  message.getFieldIfSet(order_id);
  message.getFieldIfSet(symbol);
  message.getFieldIfSet(num_fills);

  NoFills group;
  for (int idx = 1; idx <= num_fills; idx++) {
    message.getGroup(idx, group);
    FIX::FillPx fill_price;
    FIX::FillQty fill_qty;
    FIX::FillExecID fill_id;
    FIX::FillLiquidityInd fill_liq_ind;
    group.getField(fill_price);
    group.getField(fill_qty);
    group.getField(fill_id);
    group.getField(fill_liq_ind);

    ProductFillBundle& product_fill_bundle = *result_.mutable_fill_list();
    product_fill_bundle.Clear();
    auto fill = product_fill_bundle.add_each_fill();
    fill->Clear();
    fill->set_symbol(symbol.getValue());
    fill->set_fill_timestamp(msg_->timestamp);
    fill->set_exchange_order_id(order_id.getValue());
    fill->set_price(fill_price.getValue());
    fill->set_qty(fill_qty.getValue() * GetContractValue(symbol));
    fill->set_fill_id(fill_id.getValue());

    if (fill_liq_ind.getValue() == 1) {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else if (fill_liq_ind.getValue() == 2) {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else {
      LOG(INFO) << "unknown fill_liq_ind: " << fill_liq_ind.getValue();
    }
  }
  LOG(INFO) << "fix fill " << result_.mutable_fill_list()->DebugString();
  result_.set_type(MsgType::ORDER_FILL);
}

double DeribitOptionsParser::GetContractValue(const std::string& native_product) {
  using coin2::exchange::deribit_options::symbology::DeribitOptionsProduct;

  if (contract_value_map_.count(native_product) == 0) {
    auto product = DeribitOptionsProduct::FromStr(native_product, msg_->timestamp);
    auto product_holder = symcache_->GetProductHolder(product, msg_->timestamp);
    contract_value_map_[native_product] = product_holder->product_info().contract_value();
  }
  return contract_value_map_.at(native_product);
}

void DeribitOptionsParser::SetContractValue(const std::string& native_product, double val) {
  if (contract_value_map_[native_product] == val) {
    return;
  }
  SPDLOG_INFO(
      "reset contract multiplier for symbol {}: {} -> {}",
      native_product,
      contract_value_map_[native_product],
      val);
  contract_value_map_[native_product] = val;
}

void DeribitOptionsParser::ParseFixOrderInfo(const FIX::Message& message) {
  using namespace coin2::exchange::deribit::order_v2;
  FIX::OrdStatus order_state;
  if (!message.getFieldIfSet(order_state)) {
    return;
  }
  if (ORDER_STATE_NEW_ORDER != order_state && ORDER_STATE_PART_FILL != order_state &&
      ORDER_STATE_FILL != order_state) {
    return;
  }
  FIX::ClOrdID order_id;
  FIX::OrigClOrdID client_id;
  FIX::Symbol symbol;
  FIX::Price price;
  FIX::Side side;
  FIX::OrderQty order_qty;

  if (!message.getFieldIfSet(symbol)) {
    LOG(INFO) << "symbol not found";
    return;
  }
  const std::string native_symbol = symbol.getValue();
  if (!symcache_->has_product_native(native_symbol)) {
    LOG(INFO) << "[DeribitOptionsParser::ParseFixOrderInfo] ignore unknown product: "
              << native_symbol;
    return;
  }
  if (!message.getFieldIfSet(order_id)) {
    LOG(INFO) << "order_id not found";
    return;
  }
  if (!message.getFieldIfSet(price)) {
    LOG(INFO) << "price not found";
    return;
  }
  if (!message.getFieldIfSet(side)) {
    LOG(INFO) << "side not found";
    return;
  }
  if (!message.getFieldIfSet(client_id)) {
    LOG(INFO) << "client id not found";
    return;
  }
  if (!message.getFieldIfSet(order_qty)) {
    LOG(INFO) << "order_qty id not found";
    return;
  }

  int64_t proc_order_id = ToInt64(client_id.getValue());
  ProductOrderBundle& product_order_bundle = *result_.mutable_order_list();
  product_order_bundle.Clear();
  auto order = product_order_bundle.add_each_order();
  order->Clear();
  order->set_market_type(MarketType::Options);
  order->set_exchange_type(ExchangeType::Deribit);
  order->set_api_version("v2");
  order->set_exchange_order_id(native_symbol + order_id.getValue());
  order->set_proc_order_id(proc_order_id);
  order->set_symbol(native_symbol);
  order->set_price(price.getValue());
  double qty = order_qty.getValue() * GetContractValue(symbol);
  order->set_qty(qty);

  if (BUY == side.getValue()) {
    order->set_side(coin::proto::OrderSide::BUY_ORDER);
  } else if (SELL == side.getValue()) {
    order->set_side(coin::proto::OrderSide::SELL_ORDER);
  }
  order->set_timestamp(msg_->timestamp);
  switch (order_state) {
    case ORDER_STATE_NEW_ORDER:
    case ORDER_STATE_PART_FILL:
      order->set_is_live(true);
      break;
    case ORDER_STATE_FILL:
      order->set_fully_filled(true);
      break;
    default:
      LOG(ERROR) << "[ParseFIXOrderInfo] Unknown order status! " << order_state;
      break;
  }
  result_.set_type(MsgType::ORDER_STATUS);
}

auto DeribitOptionsParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  result_.Clear();
  msg_ = &msg;
  sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

  if (msg.type == OrderSubTopicId::WS_MESSAGE) {
    ParseWsMessage();
    return result_;
  }

  if (msg.type == OrderSubTopicId::QUICKFIX_MESSAGE) {
    ParseFixMessage();
    return result_;
  }

  switch (msg.http_context->query_type) {
    case OrderSubTopicId::SUBMIT_RESPONSE: {
      ParseSubmitResponse();
      break;
    }
    case OrderSubTopicId::CANCEL_RESPONSE: {
      ParseCancelResponse();
      break;
    }
    case OrderSubTopicId::LISTEN_KEY: {
      ParseRestAuthToken();
      break;
    }
    case OrderSubTopicId::REST_OPEN_ORDERS:
    case OrderSubTopicId::REST_ALL_ORDERS: {
      ParseRestOrderInfoList();
      break;
    }
    case OrderSubTopicId::REST_FILL_ORDERS: {
      ParseRestFillInfoList();
      break;
    }
    case OrderSubTopicId::REST_ACCOUNT_BALANCE: {
      ParseRestAccountBalance();
      break;
    }
    case OrderSubTopicId::REST_ACCOUNT_POSITION: {
      currency_base_ = msg_->http_context->extra_info;
      ParseRestAccountPosition();
      break;
    }
    default: {
      LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
      result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
      break;
    }
  }
  return result_;
}

}  // namespace coin2::exchange::deribit_futures::order_v2
